5.1 层与块

要点
  • 块(Module)可以理解为神经网络的一个基本组件,可以由许多层组成,也可以由许多块组成。
  • 一个块必须要实现 forward 方法,这个方法可以随意搭配其他块、随意自定义计算规则完成向前传播

1. 自定义块(Module)

一个模块(Module)是一个包含网络操作和可以持有状态(例如,权重和偏置)的高级组件。

以PyTorch为例,所有神经网络模块都应从基类 nn.Module 继扭,并需要实现 forward 方法来定义前向传播过程。例如,定义一个简单的多层感知器(MLP)可能如下所示:

import torch
from torch import nn
from torch.nn import functional as F

class MLP(nn.Module):
    # 用模型参数声明层。这里,我们声明两个全连接的层
    def __init__(self):
        # 调用MLP的父类Module的构造函数来执行必要的初始化。
        # 这样,在类实例化时也可以指定其他函数参数,例如模型参数params(稍后将介绍)
        super().__init__()
        self.hidden = nn.Linear(20, 256)  # 隐藏层
        self.out = nn.Linear(256, 10)  # 输出层

    # 定义模型的前向传播,即如何根据输入X返回所需的模型输出
    def forward(self, X):
        # 注意,这里我们使用ReLU的函数版本,其在nn.functional模块中定义。
        return self.out(F.relu(self.hidden(X)))
`super().__init__()`

创建子类实例时将自动调用父类的 __init__() 方法,但如果重写 __init__() 方法,就不会自动调用,需要加上 super().__init__() 调用父类的构造方法

net = MLP()
net(X)
为什么 `net(x)` 会自动调用 `forward` 方法?

因为会自动调用 net.__call__(X) 方法,这个方法自动计算 forward 函数

2. 顺序块

回忆 nn.Sequential 也是一种特殊的 Module,也是 Module 的子类,是顺序将各层网络串联起来:

net = nn.Sequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
X = torch.rand(2, 20)
net(X)

我们可以自己实现这种顺序块:

class MySequential(nn.Module):
    def __init__(self, *args):
        super().__init__()
        for idx, module in enumerate(args):
            # 这里,module是Module子类的一个实例。我们把它保存在'Module'类的成员
            # 变量_modules中。_module的类型是OrderedDict
            self._modules[str(idx)] = module

    def forward(self, X):
        # OrderedDict保证了按照成员添加的顺序遍历它们
        for block in self._modules.values():
            X = block(X)
        return X

*args 见 [[python中的*与**用法]]

`_modules`

self._modulesOrderedDict 类型,表示字典的 key 是有顺序的,从 Python 3.7 开始,标准的 dict 已经变成了有序的(可以简单理解为 for 循环输出顺序与字典定义顺序一致,例如上面代码第 11 行)

调用自定义顺序块:

net = MySequential(nn.Linear(20, 256), nn.ReLU(), nn.Linear(256, 10))
net(X)

参考文献



© 2023 yanghn. All rights reserved. Powered by Obsidian